「能在單台機器上把服務跑起來叫 Experiment;能跨機器自動部署與維持一致維運,才叫 Infrastructure。」
在 Day 02 中,我們完成了在 RHEL 上使用 Rootless Podman 建立安全容器環境的實作。但如果未來需要部署到多台環境,或是進行日常維運、備份與復原時,難道要每次都手動登入 SSH 敲指令嗎?
今天我們將使用 Ansible 來自動化管理整套 Angelina AI Agent 的基礎設施。
我們將探討:
為什麼選擇 Ansible 作為輕量化維運工具呢?
拆解專案中的 ansible/ 目錄真實結構與配置。
實作:ansible.cfg、hosts.yml.example 與 Playbooks (status, update, backup, restore) 自動化維運。
在 Self-hosted 的架構中,我們追求的是 Agentless 且 極輕量 的維運機制:
* Agentless 架構:Ansible 不需要事先在目標 RHEL VM 上安裝額外的背景服務(Daemon),完全透過原生的 SSH 與 Python 即可執行。這完美契合我們「$0 額外系統資源消耗」的核心原則。
* block-rescue 容錯機制:在 Playbook 中用 block-rescue 語法,在維運指令或健康檢查失敗時,第一時間捕捉錯誤(Stderr)並印出完整的 Debug 訊息,提升運維透明度。
* 版本控制與維運透明化:把所有部署、備份與巡檢流程寫成 YAML 檔納入 Git 版本控制,達成基礎設施即程式碼。
在 Angelina 專案中,我們將維運任務模組化,收納於 ansible/ 結構中:
Plaintext
angelina-finance-agent/
└── ansible/
├── ansible.cfg # Ansible 全局連線與行為設定檔
├── inventory/
│ └── hosts.yml.example # 主機清單範例檔 (YAML 格式)
└── playbooks/
├── status.yml # 容器狀態、API Health Check 與 Vector Stats 巡檢
├── update.yml # 執行 update.sh 與健康檢查自動重試
├── backup.yml # 執行 backup.sh 自動化備份
└── restore.yml # 執行 restore.sh 與復原驗證
在 ansible/ 目錄下的 ansible.cfg 定義預設 inventory 路徑與連線參數:
Ini, TOML
[defaults]
inventory = inventory/hosts.yml
host_key_checking = False
timeout = 30
remote_tmp = /tmp/.ansible/tmp
[privilege_escalation]
become = False
專案提供 YAML 格式的範例檔,改名為 hosts.yml 使用,定義專屬的 agents 主機群組與部署參數:
YAML
all:
children:
agents:
hosts:
angelina:
ansible_host: YOUR_VM_IP
ansible_user: YOUR_SSH_USER
ansible_password: YOUR_SSH_PASSWORD
ansible_become_password: YOUR_SUDO_PASSWORD
app_dir: /opt/angelina
container_name: angelina-app
image_name: "angelina:latest"
vars:
ansible_connection: ssh
ansible_python_interpreter: /usr/bin/python3
這個 Playbook 不僅檢查 Podman 容器運行狀態與磁碟空間,還會透過 HTTP API 驗證 Agent 的 /health 與對話記憶/向量資料庫筆數(/stats):
YAML
---
- name: Check status of all agent VMs
hosts: agents
gather_facts: false
tasks:
- name: Check container running
ansible.builtin.shell: "podman ps --all --format 'table {{'{{'}} .Names {{'}}'}} {{'{{'}} .Status {{'}}'}}' 2>/dev/null || echo 'podman not available'"
register: container_status
changed_when: false
ignore_errors: true
- name: Container result
ansible.builtin.debug:
msg: "{{ container_status.stdout_lines | default(['unknown']) }}"
- name: Check health
ansible.builtin.uri:
url: "http://{{ ansible_host }}:8080/health"
method: GET
timeout: 10
register: health
ignore_errors: true
- name: Health result
ansible.builtin.debug:
msg: "Health: {{ health.json.status | default('UNREACHABLE') }}"
- name: Get stats
ansible.builtin.uri:
url: "http://{{ ansible_host }}:8080/stats"
method: GET
timeout: 10
register: stats
ignore_errors: true
- name: Stats result
ansible.builtin.debug:
msg: "Turns: {{ stats.json.memory_turns | default('N/A') }}, Vectors: {{ stats.json.vector_count | default('N/A') }}"
when: stats is succeeded
- name: Disk usage
ansible.builtin.shell: "df -h {{ app_dir }} | tail -1"
register: disk
changed_when: false
- name: Disk result
ansible.builtin.debug:
msg: "Disk: {{ disk.stdout }}"
透過呼叫 deploy/update.sh 更新腳本,並搭配 5 次 Retry 的健康檢查,確保服務重啟成功:
YAML
---
# Update Playbook - Update and redeploy agent VMs
- name: Update and redeploy all agent VMs
hosts: agents
gather_facts: false
tasks:
- name: Update and redeploy
block:
- name: Execute update script
ansible.builtin.shell: bash {{ app_dir }}/deploy/update.sh
register: update_result
changed_when: true
- name: Print update output
ansible.builtin.debug:
msg: |
===== UPDATE OUTPUT for {{ inventory_hostname }} =====
{{ update_result.stdout }}
================================================
- name: Wait for service to start
ansible.builtin.pause:
seconds: 10
- name: Verify health after update
ansible.builtin.uri:
url: "http://localhost:8080/health"
method: GET
timeout: 30
register: health_check
retries: 5
delay: 5
until: health_check.status == 200
- name: Report update success
ansible.builtin.debug:
msg: |
===== UPDATE RESULT for {{ inventory_hostname }} =====
Status: SUCCESS
Health check: PASSED
================================================
rescue:
- name: Report update failure
ansible.builtin.debug:
msg: |
===== UPDATE RESULT for {{ inventory_hostname }} =====
Status: FAILED
Error: {{ ansible_failed_result.msg | default('Unknown error') }}
Stderr: {{ update_result.stderr | default('N/A') }}
================================================
在實際營運時,我們可以直接切換至 ansible/ 目錄,透過以下指令進行自動化管理:
# 1. 複製主機設定檔範例
cp ansible/inventory/hosts.yml.example ansible/inventory/hosts.yml
# 2. 跨主機檢查系統運作狀態、API Health 及記憶/向量庫筆數
ansible-playbook ansible/playbooks/status.yml
# 3. 快速更新應用程式碼、重啟容器並自動驗證 Health Check
ansible-playbook ansible/playbooks/update.yml
# 4. 遠端執行自動化備份 Playbook
ansible-playbook ansible/playbooks/backup.yml
# 5. 遠端執行災難復原 Playbook (可帶入特定備份路徑,預設為 latest)
ansible-playbook ansible/playbooks/restore.yml -e "backup_path=latest"
透過 Ansible 的導入,我們補齊了基礎設施自動化的關鍵拼圖:
擺脫手動維運:透過 ansible.cfg 與 YAML 格式的 inventory,徹底實現維運規格程式碼化。
完整生命週期與 Health-check 閉環:從 status.yml 巡檢、update.yml 更新,到 backup.yml / restore.yml 災難復原,結合 HTTP API 狀態檢查,確保每一個步驟都可驗證。
高可擴展性:未來無論是要擴展到 2 台還是 10 台 RHEL VM,都只需要在 hosts.yml 的 agents 群組中新增主機設定即可一鍵維運。
到這裡,Week 1 的「硬核基礎設施與安全隔離」章節已順利完結!
明天(Day 04)開始,我們將正式進入 AI Agent 的核心大腦——Gemini 2.5 Flash API 整合、Model Fallback 自動降級備援,以及重試機制的實務設計!
明日預告:【Day 04】AI 推理核心:Gemini 2.5 Flash API 整合與 Model Fallback 自動降級備援機制